package com.itheima.prize.msg;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.itheima.prize.commons.config.RedisKeys;
import com.itheima.prize.commons.db.entity.*;
import com.itheima.prize.commons.db.service.CardGameProductService;
import com.itheima.prize.commons.db.service.CardGameRulesService;
import com.itheima.prize.commons.db.service.CardGameService;
import com.itheima.prize.commons.db.service.GameLoadService;
import com.itheima.prize.commons.utils.RedisUtil;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 活动信息预热，每隔1分钟执行一次
 * 查找未来1分钟内（含），要开始的活动
 */
@Component
public class GameTask {
    private final static Logger log = LoggerFactory.getLogger(GameTask.class);
    @Autowired
    private CardGameService gameService;
    @Autowired
    private CardGameProductService gameProductService;
    @Autowired
    private CardGameRulesService gameRulesService;
    @Autowired
    private GameLoadService gameLoadService;
    @Autowired
    private RedisUtil redisUtil;

    @Scheduled(cron = "0 * * * * ?")
    public void execute() {
        // 获取当前时间的Calendar实例
        Calendar calendar = Calendar.getInstance();
        // 清除毫秒部分
        calendar.set(Calendar.MILLISECOND, 0);
        // 获取不带毫秒的Date对象
        Date now = calendar.getTime();

        //查询将来1分钟内要开始的活动
        QueryWrapper<CardGame> queryWrapper1 = new QueryWrapper<>();
        //开始时间大于当前时间
        queryWrapper1.gt("starttime",now);
        //小于等于（当前时间+1分钟）
        queryWrapper1.le("starttime",DateUtils.addMinutes(now,1));
        List<CardGame> gameList = gameService.list(queryWrapper1);

        if(gameList.size() == 0){
            //没有查到要开始的活动
            log.info("game list scan : size = 0");
            return;
        }
        log.info("game list scan : size = {}",gameList.size());

        //有相关活动数据，则将活动数据预热，进redis
        gameList.forEach(game -> {
                    //活动开始时间  getTime():从日期起经过的毫秒数差
                    long start = game.getStarttime().getTime();
                    //活动结束时间
                    long end = game.getEndtime().getTime();
                    //计算活动结束时间到现在还有多少秒，作为redis key过期时间
                    long expire = (end - now.getTime()) / 1000;
                    // long expire = -1; //永不过期
                    //活动持续时间（ms）
                    long duration = end - start;

                    Map queryMap = new HashMap();
                    queryMap.put("gameid", game.getId());

                    //将活动的状态设置为已加载
                    game.setStatus(1);
                    //将活动放进缓存预热
                    redisUtil.set(RedisKeys.INFO + game.getId(), game, -1);
                    log.info("load game info:{},{},{},{}", game.getId(), game.getTitle(), game.getStarttime(), game.getEndtime());

                    //查询奖品信息
                    List<CardProductDto> cardProductDtoList = gameLoadService.getByGameId(game.getId());
                    //设置k-v    奖品id - 奖品
                    Map<Integer,CardProductDto> productMap = new HashMap<>(cardProductDtoList.size());
                    cardProductDtoList.forEach(a->productMap.put(a.getId(),a));
                    log.info("load product type:{}",productMap.size());

                    //查询活动奖品的数量等配置信息
                    List<CardGameProduct> gameProductList=gameProductService.listByMap(queryMap);
                    log.info("load bind product:{}",gameProductList.size());


                   //令牌桶
                   List<Long> tokenList=new ArrayList<>();
                   //根据奖品生成令牌，并放入令牌桶
                   gameProductList.forEach(a->{
                       //生成amount个start到end之间的随机时间戳做令牌
                       for (int i=0;i<a.getAmount();i++){
                           long rnd = start+new Random().nextInt((int)duration);
                           //乘1000 再加一个任意的三位数 减少重复的概率
                           long token =rnd*1000+new Random().nextInt(999);
                           //将令牌放入令牌桶
                           tokenList.add(token);
                           //以令牌做key，对应的商品为value，创建redis缓存
                           log.info("token -> game : {} -> {}",token/1000 ,productMap.get(a.getProductid()).getName());
                           //token到实际奖品之间建立映射关系
                           redisUtil.set(RedisKeys.TOKEN+game.getId()+"_"+token,productMap.get(a.getProductid()),expire);
                       }
                   });
                   //对令牌进行从小到大排序
                   Collections.sort(tokenList);
                   log.info("load tokens:{}",tokenList);

                   //从右往左依次压入双端队列,从左到右，时间戳逐渐增大
                   redisUtil.rightPushAll(RedisKeys.TOKENS+game.getId(),tokenList);
                   //指定缓存失效时间
                   redisUtil.expire(RedisKeys.TOKENS+game.getId(),expire);

                   //配置活动的策略
                   List<CardGameRules> cardGameRulesList=gameRulesService.listByMap(queryMap);
                   cardGameRulesList.forEach(a->{
                       redisUtil.hset(RedisKeys.MAXGOAL+game.getId(), a.getUserlevel(), a.getGoalTimes());
                       redisUtil.hset(RedisKeys.MAXENTER+game.getId(), a.getUserlevel(), a.getEnterTimes());
                       redisUtil.hset(RedisKeys.RANDOMRATE+game.getId(), a.getUserlevel(), a.getRandomRate());
                       log.info("load rules: userLevel={} enterTimes={} goalTimes={} randomRate={}"
                               ,a.getUserlevel(),a.getEnterTimes(),a.getGoalTimes(),a.getRandomRate());
                   });
                   //指定策略的失效时间
                  redisUtil.expire(RedisKeys.MAXGOAL +game.getId(),expire);
                  redisUtil.expire(RedisKeys.MAXENTER +game.getId(),expire);
                  redisUtil.expire(RedisKeys.RANDOMRATE +game.getId(),expire);

                   //活动状态变更为已预热，
                   game.setStatus(1);
                   gameService.updateById(game);
                });

            System.out.printf("scheduled!"+new Date());
        //TODO
    }
}
